#!/usr/bin/env python3

# Copyright (c) 2020-2021 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


import os
import re
import sys
import shutil
import sqlglot
import calendar
from datetime import datetime
from gi.repository import Gio
from gi.repository import GLib


class Util:

    @staticmethod
    def sqliteHasTables(dbConn, *tableNames):
        for tableName in tableNames:
            c = dbConn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (tableName,))
            if c.fetchone() is None:
                return False
        return True

    @staticmethod
    def sqliteCheckTables(dbConn, sql):
        for item in sqlglot.parse(sql):
            # FIXME
            pass

        # FIXME: should do extensively check
        for m in re.finditer(r"CREATE TABLE (.*) \(", sql):
            tableName = m.group(1)
            c = dbConn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (tableName,))
            if c.fetchone() is None:
                return False
        return True

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def forceMkdir(path, clear=False):
        if os.path.islink(path):
            os.remove(path)
            os.mkdir(path)
        elif os.path.isfile(path):
            os.remove(path)
            os.mkdir(path)
        elif os.path.isdir(path):
            if clear:
                shutil.rmtree(path)
                os.mkdir(path)
            else:
                pass
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
            os.mkdir(path)
        else:
            os.mkdir(path)              # path does not exist

    @staticmethod
    def dateTime2utcTimestamp(src):
        date_time = datetime.utcnow()
        return calendar.timegm(date_time.utctimetuple())

    @staticmethod
    def rangeMayInfinite(start, end):
        assert start == 0 and end >= 0

        i = 0
        while end == 0 or i < end:
            yield i
            if i >= sys.maxsize:
                raise ValueError("value too large")
            i += 1


class HelperWildcard:

    @classmethod
    def check_patterns(cls, wildcards):
        for w in wildcards:
            if w[0] != '/':
                return False
            if "*" in w and not w.endswith("/***") and not w.endswith("/**"):
                return False
        return True

    @classmethod
    def match_patterns(cls, name, wildcards):
        return any([cls.match_pattern(name, x) for x in wildcards])

    @classmethod
    def match_pattern(cls, name, wildcard):
        p = wildcard
        if p.endswith("/***"):
            p = os.path.dirname(p)
            if name == p or name.startswith(cls._pathAddSlash(p)):
                return True
        elif p.endswith("/**"):
            p = os.path.dirname(p)
            if name.startswith(cls._pathAddSlash(p)):
                return True
        else:
            if name == p:
                return True
        return False

    @staticmethod
    def _pathAddSlash(path):
        if path == "/":
            return path
        else:
            return path + "/"


class AvahiServiceBrowser:

    """
    Exampe:
        obj = AvahiServiceBrowser("_http._tcp")
        obj.run()
        obj.get_result_list()
    """

    def __init__(self, service):
        self.service = service

    def run(self):
        self._result_dict = dict()

        self._server = None
        self._browser = None
        self._error_message = None
        try:
            self._server = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SYSTEM,
                                                          Gio.DBusProxyFlags.NONE,
                                                          None,
                                                          "org.freedesktop.Avahi",
                                                          "/",
                                                          "org.freedesktop.Avahi.Server")

            path = self._server.ServiceBrowserNew("(iissu)",
                                                  -1,                                   # interface = IF_UNSPEC
                                                  0,                                    # protocol = PROTO_INET
                                                  self.service,                         # type
                                                  "",                                   # domain
                                                  0)                                    # flags
            self._browser = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SYSTEM,
                                                           Gio.DBusProxyFlags.NONE,
                                                           None,
                                                           "org.freedesktop.Avahi",
                                                           path,
                                                           "org.freedesktop.Avahi.ServiceBrowser")
            self._browser.connect("g-signal", self._signal_handler)

            self._mainloop = GLib.MainLoop()
            self._mainloop.run()
            if self._error_message is not None:
                raise Exception(self._error_message)
        except GLib.Error as e:
            # treat dbus error as success but with no result
            if e.domain in ["g-io-error-quark", "g-dbus-error-quark"]:
                return
            raise
        finally:
            self._error_message = None
            if self._browser is not None:
                self._browser.Free()
                self._browser = None
            self._server = None

    def get_result_list(self):
        return self._result_dict.values()

    def _signal_handler(self, proxy, sender, signal, param):
        if signal == "ItemNew":
            interface, protocol, name, stype, domain, flags = param.unpack()
            self._server.ResolveService("(iisssiu)",
                                        interface,
                                        protocol,
                                        name,
                                        stype,
                                        domain,
                                        -1,                                     # interface = IF_UNSPEC
                                        0,                                      # protocol = PROTO_INET
                                        result_handler=self._service_resolved,
                                        error_handler=self._failure_handler)

        if signal == "ItemRemove":
            interface, protocol, name, stype, domain, flags = param.unpack()
            key = (interface, protocol, name, stype, domain)
            if key in self._result_dict:
                del self._result_dict[key]

        if signal == "AllForNow":
            self._mainloop.quit()

        if signal == "Failure":
            self._failure_handler(param)

        return True

    def _service_resolved(self, proxy, result, user_data):
        interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags = result
        key = (interface, protocol, name, stype, domain)
        self._result_dict[key] = (name, address, int(port))

    def _failure_handler(self, error):
        self._error_message = error
        self._mainloop.quit()
